package com.atguigu.userprofile.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.userprofile.bean.TagCondition;
import com.atguigu.userprofile.bean.TagInfo;
import com.atguigu.userprofile.bean.UserGroup;
import com.atguigu.userprofile.constants.ConstCodes;
import com.atguigu.userprofile.mapper.UserGroupMapper;
import com.atguigu.userprofile.service.TagInfoService;
import com.atguigu.userprofile.service.UserGroupService;
import com.atguigu.userprofile.utils.RedisUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;

import org.apache.catalina.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zhangchen
 * @since 2021-05-04
 */
@Service
@Slf4j
@DS("mysql")
public class UserGroupServiceImpl extends ServiceImpl<UserGroupMapper, UserGroup> implements UserGroupService {

    @Autowired
    TagInfoService tagInfoService;



    @Override
    public void saveUserGroupInfo(UserGroup userGroup ,boolean isRefresh) {
            //1 条件集合的json
        List<TagCondition> tagConditionList = userGroup.getTagConditions();
        String conditionJson = JSON.toJSONString(tagConditionList);
        userGroup.setConditionJsonStr(conditionJson);
        //2  条件的中文说明
        List<String> conditionList = tagConditionList.stream().map(tagCondition -> tagCondition.getTagName() + " " + tagCondition.getOperatorName() + StringUtils.join(tagCondition.getTagValues() , ","))
                .collect(Collectors.toList());

        String conditionComment = StringUtils.join(conditionList, ";");
        userGroup.setConditionComment(conditionComment);
        //3  创建时间
        if(isRefresh){
            userGroup.setUpdateTime(new Date());
        }else{
            userGroup.setCreateTime(new Date());
        }


        saveOrUpdate(userGroup);

    }


    //负责执行生成好的insert sql
    @Override
    public void genUserGroup(UserGroup userGroup) {
        // 幂等性处理 ：  把之前的清除
        //  alter table user_group delete where user_group_id =''
        baseMapper.deleteUserGroup(userGroup.getId()+"");

        String insertBitmapSQL = genInsertBitmapSQL(userGroup);
        super.baseMapper.insertBitmapSQL(insertBitmapSQL);

    }

    @Override
    public Integer saveUserGroupToRedis(UserGroup userGroup) {
        // 1 读 clickhouse    把clickhouse读取到java 集合对象中
        //   问题：由于clickhouse的清理操作时异步的  ，redis查询时，旧数据可能没有真正被清理，
        //造成：查询出旧数据
        //解决： redis不依赖clickhouse的写入结果，要更新redis时 ，重新进行bitmap的计算 结果直接读入java ，然后写入redis.
        String bitmapAndSQL = genBitmapAndSQL(userGroup.getTagConditions(), userGroup.getBusiDate());

        //List<String> uidList=baseMapper.selectBitmapToListBySQL(userGroup.getId()+""); 有问题
        List<String> uidList=baseMapper.selectBitmapToListBySQL(bitmapAndSQL);



        System.out.println(uidList);
        // 2 写 redis     再把集合对象 写入redis
        //  type ?     string(1对1)  list(队列)   set (判存，不重复) 选择    zset(排序? )    hash(键值对？ 单值？)
        // key ?  user_group:101   value?   uid...    field/score? 无
         // 写入 api  sadd     读api  smembers 全读  sismember 判存
        // 超时？  不加超时

        Jedis jedis = RedisUtil.getJedis();

        String userGroupKey="user_group:"+userGroup.getId();
        //幂等性处理  删除原来的分群
        jedis.del(userGroupKey);

        String[] uidArr = uidList.toArray(new String[]{});
        jedis.sadd(userGroupKey, uidArr);


        jedis.close();

        return uidArr.length;
    }

    //估算人数
    // select bitmapCardinality(bitmapAndSQL) ;
    @Override
    public Integer evaluateUserGroupNum(UserGroup userGroup) {
        String bitmapAndSQL = genBitmapAndSQL(userGroup.getTagConditions(), userGroup.getBusiDate());
        String sql="select bitmapCardinality( "+bitmapAndSQL+")";
        Integer userGroupNum = baseMapper.selectUserGroupNum(sql);
        return userGroupNum;
    }

    @Override
    public UserGroup getUserGroupInfoById(String userGroupId) {
        UserGroup userGroup = getById(userGroupId);
        String conditionJsonStr = userGroup.getConditionJsonStr();
        List<TagCondition> tagConditionList = JSON.parseArray(conditionJsonStr, TagCondition.class);
        userGroup.setTagConditions(tagConditionList);
        return userGroup;
    }


    //    insert into user_group
    //    select    $bitmapAndSQL
        //     ;
    private  String genInsertBitmapSQL(UserGroup userGroup){
        List<TagCondition> tagConditionList = userGroup.getTagConditions();
        String busiDate = userGroup.getBusiDate();
        String bitmapAndSQL = genBitmapAndSQL(tagConditionList, busiDate);
        String insertSQL="insert into user_group  select '"+userGroup.getId()+"',"+bitmapAndSQL;
        System.out.println(insertSQL);
        return insertSQL;
    }


    //       bitmapAnd(
    //         bitmapAnd (
    //          subquerySQL1 ,
    //         subquerySQL2   )
    //        ,
    //        subquerySQL3   ));
    private  String genBitmapAndSQL(List<TagCondition> tagConditionList ,String busiDate){

        Map<String,TagInfo > tagInfoMap =tagInfoService.getTagInfoMapWithCode();
        StringBuilder sqlBuilder=new StringBuilder();
        for (TagCondition tagCondition : tagConditionList) {
            String subQuerySQL = genSubQuerySQL(tagCondition, busiDate, tagInfoMap);
            if(sqlBuilder.length()==0){  //第一条subquery
                sqlBuilder.append(subQuerySQL);
            }else{
                sqlBuilder.insert(0,"bitmapAnd(").append(","+subQuerySQL+")");
            }
        }
        return  sqlBuilder.toString();

    }


   //    (select groupBitmapMergeState(us)  from user_tag_value_string  where tag_code='tg_person_base_gender'  and tag_value='女'  and dt='2020-06-14')
    //  (select groupBitmapMergeState(us)  from user_tag_value_string  where tag_code='tg_person_base_agegroup'  and tag_value in ('70后','80后','90后')  and dt='2020-06-14')
    //  (select groupBitmapMergeState(us)  from user_tag_value_decimal  where tag_code='tg_behavior_order_amount_7d'  and tag_value > 100  and dt='2020-06-14')
    //// 1  表名   由标签值类型决定 -->   利用tag_code 去  tag_info 表中查询  --> tag_value_type 进行判断 获得表名
    //  2  tag_code 要从TagCondition中 tagCode 获得 转小写
    // 3 tag_value的符号 把英文符号 转为  比较符
    // 4 tag_value 的值    1  一个或者多个 ，多个需要拼接  2 字符串需要补单引  tag_value_type 进行判断  3 如果是 in  nin  都需要加小括号
    //5  dt  需要获得busiDate
    private String  genSubQuerySQL(TagCondition tagCondition,String busiDate ,Map<String,TagInfo > tagInfoMap ){
        String tagCode = tagCondition.getTagCode();
        //// 1  表名   由标签值类型决定 -->   利用tag_code 去  tag_info 表中查询  --> tag_value_type 进行判断 获得表名

        TagInfo tagInfo = tagInfoMap.get(tagCode);
        String tagValueType = tagInfo.getTagValueType();  //从map中获得tagInfo  中的 tagValueType
        String tableName="";
        if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_STRING)){
            tableName="user_tag_value_string";
        }else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_LONG)){
            tableName="user_tag_value_long";
        }else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DECIMAL)){
            tableName="user_tag_value_decimal";
        }else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DATE)){
            tableName="user_tag_value_date";
        }

        // 3 tag_value的符号 把英文符号 转为  比较符
        String conditionOperator = getConditionOperator(tagCondition.getOperator());
        // 4 tag_value 的值    1  一个或者多个 ，多个需要拼接  2 字符串\日期需要补单引  tag_value_type 进行判断  3 如果是 in  nin  都需要加小括号
        List<String> tagValueList = tagCondition.getTagValues();
        String tagValueSQL =null;
        if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_STRING)||tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DATE)){
              tagValueSQL = "'" + StringUtils.join(tagValueList, "','") + "'";
        }else{
              tagValueSQL = StringUtils.join(tagValueList, ",");
        }
        if(tagCondition.getOperator().equals("in")||tagCondition.getOperator().equals("nin")){
            tagValueSQL="("+tagValueSQL+")";
        }
        //5  dt  需要获得busiDate
        String dt=busiDate;

        // sql
        String subquerySQL="(select groupBitmapMergeState(us) " +
                " from  " +tableName+
                " where tag_code='"+tagCode.toLowerCase()+"' " +
                " and tag_value   " +conditionOperator+ " "+tagValueSQL+
                " and dt='"+dt+"')\n" ;

        return subquerySQL;


    }

    // 把英文的操作代码转换为 判断符号
    private  String getConditionOperator(String operator){
        switch (operator){
            case "eq":
                return "=";
            case "lte":
                return "<=";
            case "gte":
                return ">=";
            case "lt":
                return "<";
            case "gt":
                return ">";
            case "neq":
                return "<>";
            case "in":
                return "in";
            case "nin":
                return "not in";
        }
        throw  new RuntimeException("操作符不正确");
    }

}
